package monitor.view.bean;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import org.primefaces.model.chart.CartesianChartModel;
import org.primefaces.model.chart.ChartSeries;
import org.primefaces.model.map.DefaultMapModel;
import org.primefaces.model.map.LatLng;
import org.primefaces.model.map.MapModel;
import org.primefaces.model.map.Polyline;

import com.google.appengine.api.datastore.Entity;

import monitor.model.dao.CarDAO;
import monitor.model.dao.HistoryDAO;
import monitor.model.entity.Car;
import monitor.model.parameter.EcuParameterType;
import monitor.model.parameter.TimePoint;

/**
 * Manage ECU data show for selected car.
 *
 */
@ManagedBean(name = "historyController")
@ViewScoped
public class HistoryController extends Base {
	private static final long serialVersionUID = 9177872936920935324L;
	
	private static final String ACTION_CHART = "CHART";
	private static final String ACTION_MAP = "MAP";
	private String action = ACTION_CHART;
	
	private CartesianChartModel chartModel = new CartesianChartModel();
	private MapModel polylineModel = new DefaultMapModel();
	private Car car;
	private EcuParameterType ecuParam = EcuParameterType.TEMPERATURE;
	private int page;
	private int pageSize = 30;
	private Date date = new Date();
	private Date timeFrom = new Date();
	private Date timeTo = new Date(new Date().getTime() + 60000*60*2); //+2 hours
	private String mapCenter = "42.700604, 23.320370";//SOFIA
	private boolean paramDataFound;
	
	/**
	 * Load car with given car id. 
	 * If current user is not owner of the car, user is redirected to access denied page.
	 */
	@PostConstruct
	public void init() {
		String carId = getRequest().getParameter("carId");
		if(carId != null) {
			this.car = CarDAO.getInstance().getCarById(carId);
			if(this.car == null) {
				ExternalContext c = FacesContext.getCurrentInstance().getExternalContext();
				try {
					c.redirect("access-denied.jsf");
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		TimeZone.setDefault(TimeZone.getTimeZone("GMT+3"));
	}
	
	/**
	 * Add SEVERITY_INFO UI message for JSF user
	 * 
	 * @param title of the message
	 * @param description of the message
	 */
	public void addMessage(String title, String description) {
		FacesMessage msg = 
				new FacesMessage(FacesMessage.SEVERITY_INFO, title, description);
		FacesContext.getCurrentInstance().addMessage(null, msg);
	}
	
	/**
	 * Load chart or map, whether ECU or Location is selected to be shown
	 */
	public void executeAction() {
		if(timeFrom.after(timeTo)) {
			addMessage("Time isn't correct", "Please enter correct time interval");
			return;
		}
		
		this.page = 0;
		
		if(action.equals(ACTION_MAP)) {
			loadMapPage();
		}
		if(action.equals(ACTION_CHART)) {
			loadChartPage();
		}
	}
	
	/**
	 * Used in paging. Load next page.
	 */
	public void next() {
		this.page++;
		
		if(action.equals(ACTION_MAP)) {
			loadMapPage();
		}
		if(action.equals(ACTION_CHART)) {
			loadChartPage();
		}
	}
	
	/**
	 * Used in paging. Load previous page.
	 */
	public void previous() {
		this.page--;

		if(action.equals(ACTION_MAP)) {
			loadMapPage();
		}
		if(action.equals(ACTION_CHART)) {
			loadChartPage();
		}
	}
	
	/**
	 * Method is invoked to display location on the map
	 */
	private void loadMapPage() {
		polylineModel = new DefaultMapModel();  
		HistoryDAO dao = HistoryDAO.getInstance();
		List<Entity> entities = 
				dao.retrieveAllEntitiesInRange(this.car.getId(), this.date, this.timeFrom, this.timeTo, this.page, this.pageSize);
		Polyline polyline = new Polyline();
		LatLng coord = null;
		this.paramDataFound = false;
		for (Entity entity : entities) {
			this.paramDataFound = true;
			Double lat = (Double)entity.getProperty(TimePoint.LAT);
			Double lng = (Double)entity.getProperty(TimePoint.LNG);
			
			coord = new LatLng(lat, lng);
			polyline.getPaths().add(coord);
		}
		
        polyline.setStrokeWeight(10);  
        polyline.setStrokeColor("#FF9900");  
        polyline.setStrokeOpacity(0.7);
        
        polylineModel.addOverlay(polyline); 
	}
	
	/**
	 * Method is invoked to show ECU data on chart
	 */
	private void loadChartPage() {
		this.chartModel = new CartesianChartModel();
		if(car != null) {
			HistoryDAO dao = HistoryDAO.getInstance();
			List<Entity> entities = 
					dao.retrieveAllEntitiesInRange(this.car.getId(), this.date, this.timeFrom, this.timeTo, this.page, this.pageSize);
			ChartSeries avg = new ChartSeries();
			ChartSeries max = new ChartSeries();
			avg.setLabel("Average " + this.ecuParam.getDimension());
			max.setLabel("Max " + this.ecuParam.getDimension());
	
			this.paramDataFound = false;
			for (Entity entity : entities) {
				Double yAvg = (Double)entity.getProperty(this.ecuParam.getNameAvg());
				Double yMax = (Double)entity.getProperty(this.ecuParam.getNameMax());
				
				if(yAvg == null || yMax == null) {
					continue;
				}
				this.paramDataFound = true;
				
				Long dateMillis = (Long)entity.getProperty(TimePoint.DATE_MILLIS);
				Date date = new Date(dateMillis);
				SimpleDateFormat dateFormat = (SimpleDateFormat)SimpleDateFormat.getInstance();
				dateFormat.applyPattern("HH:mm");
				String xTime = dateFormat.format(date);
				
				avg.set(xTime, yAvg);
				max.set(xTime, yMax);
			}
	
			if(!this.paramDataFound && entities.size() > 0) {
				addMessage("No data for this param", this.ecuParam.name() + "\nis not synchronised. " +
						"But there is information for the other parameters in this time interval!");
			}
			chartModel.addSeries(avg);
			chartModel.addSeries(max);
		}
	}
	
	public MapModel getPolylineModel() {
        return polylineModel;  
    }
	
	/**
	 * Used for paging
	 * @return true if there is more data
	 */
	public boolean isNextDisabled() {
		if(this.paramDataFound) {
			return false;
		}
		return true;
	}
	
	/**
	 * Used for paging
	 * @return true if page is first page
	 */
	public boolean isPreviousDisabled() {
		if(this.page == 0) {
			return true;
		}
		return false;
	}
	
	
	/**
	 * @return all available ECU parameters
	 */
	public EcuParameterType[] getAllParameters() {
		return EcuParameterType.values();
	}
	
	public CartesianChartModel getChart() {
		return this.chartModel;
	}
	
	public Date getDate() {
		return this.date;
	}

	public void setDate(Date date) {
		this.date = date;
	}

	public EcuParameterType getEcuParam() {
		return ecuParam;
	}

	public void setEcuParam(EcuParameterType ecuParam) {
		this.ecuParam = ecuParam;
	}
	
	public Car getCar() {
		return car;
	}

	public void setCar(Car car) {
		this.car = car;
	}

	public Date getTimeFrom() {
		return this.timeFrom;
	}

	public void setTimeFrom(Date timeFrom) {
		this.timeFrom = timeFrom;
	}

	public Date getTimeTo() {
		return this.timeTo;
	}

	public void setTimeTo(Date timeTo) {
		this.timeTo = timeTo;
	}

	/**
	 * If location data is not found, this method is invoked for default map center
	 * @return location of Sofia
	 */
	public String getMapCenter() {
		if(polylineModel != null && polylineModel.getPolylines() != null) {
			List<LatLng> coords = polylineModel.getPolylines().get(0).getPaths();
			if(coords.size() > 0) {
				LatLng coord = coords.get(0);
				return coord.getLat() + ", " + coord.getLng();
			}
		}
		
		return mapCenter;
	}

	public void setMapCenter(String mapCenter) {
		this.mapCenter = mapCenter;
	}
	
	/**
	 * @return List of location points - track
	 */
	public List<LatLng> getCoordinates() {
		if(polylineModel != null && polylineModel.getPolylines() != null) {
			if(polylineModel.getPolylines().size() > 0) {
				List<LatLng> coords = polylineModel.getPolylines().get(0).getPaths();
				return coords;
			}
		}
		return new ArrayList<LatLng>();
	}

	public int getPageSize() {
		return pageSize;
	}

	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}

	public boolean getParamDataFound() {
		return paramDataFound;
	}

	public void setParamDataFound(boolean paramDataFound) {
		this.paramDataFound = paramDataFound;
	}

	public String getAction() {
		return action;
	}

	public void setAction(String action) {
		this.action = action;
	}
	
}
